
基于SFINAE的特征的另一个尝试涉及创建特征(或者更确切地说，一组特征)，可以确定给定的类型T是否具有给定名称X的成员(类型成员还是非类型成员)。

\subsubsubsection{19.6.1\hspace{0.2cm}检查成员类型}

首先定义一个特征，可以确定给定类型T是否有成员类型size\_type:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{traits/hassizetype.hpp}
\begin{lstlisting}[style=styleCXX]
#include <type_traits> // defines true_type and false_type

// helper to ignore any number of template parameters:
template<typename...> using VoidT = void;

// primary template:
template<typename, typename = VoidT<>>
struct HasSizeTypeT : std::false_type
{
};

// partial specialization (may be SFINAE’d away):
template<typename T>
struct HasSizeTypeT<T, VoidT<typename T::size_type>> : std::true_type
{
};
\end{lstlisting}

使用SFINAE的方法剔除了偏特化。

与通常的谓词特征一样，定义从std::false\_type派生出一般情况，因为默认情况下类型没有成员size\_type。

本例中，只需要一个构造:

\begin{lstlisting}[style=styleCXX]
typename T::size_type
\end{lstlisting}

当类型T具有成员类型size\_type时，该构造有效，这是我们试图确定的。若对于特定的T，构造无效(类型T没有成员类型size\_type)， SFINAE会丢弃偏特化，会返回到主模板。否则，偏特化有效，并且是首选。

可以这样使用这个特征:

\begin{lstlisting}[style=styleCXX]
std::cout << HasSizeTypeT<int>::value; // false
struct CX {
	using size_type = std::size_t;
};
std::cout << HasSizeType<CX>::value; // true
\end{lstlisting}

若成员类型size\_type私有，HasSizeTypeT会产生false，因为特征模板没有对参数类型的访问权限，因此typename T::size\_type无效(触发SFINAE)。换句话说，特征会测试是否有可访问的成员类型size\_type。

\hspace*{\fill} \\ %插入空行
\noindent
\textbf{处理引用类型}

作为开发者，我们会考虑“临界”情况可能会出现的问题。对于像HasSizeTypeT这样的特征模板，引用类型可能会产生一些问题。例如，下面的代码工作很好:

\begin{lstlisting}[style=styleCXX]
struct CXR {
	using size_type = char&; // Note: type size_type is a reference type
};
std::cout << HasSizeTypeT<CXR>::value; // OK: prints true
\end{lstlisting}

以下代码则会失败:

\begin{lstlisting}[style=styleCXX]
std::cout << HasSizeTypeT<CX&>::value; // OOPS: prints false
std::cout << HasSizeTypeT<CXR&>::value; // OOPS: prints false
\end{lstlisting}

这就很神奇。引用类型本身没有成员，但每当使用引用时，得到的表达式都有底层类型，因此考虑底层类型可能会更好。可以通过在HasSizeTypeT的偏特化中使用之前的RemoveReference特征来实现:

\begin{lstlisting}[style=styleCXX]
template<typename T>
struct HasSizeTypeT<T, VoidT<RemoveReference<T>::size_type>>
: std::true_type {
};
\end{lstlisting}

\hspace*{\fill} \\ %插入空行
\noindent
\textbf{注入类名}

用来检测成员类型的特征也会为注入类名生成一个真实值(参见第13.2.3节)。例如:

\begin{lstlisting}[style=styleCXX]
struct size_type {
};

struct Sizeable : size_type {
};

static_assert(HasSizeTypeT<Sizeable>::value,
			"Compiler bug: Injected class name missing");
\end{lstlisting}

因为size\_type引入自己的名称作为成员类型，并且继承该名称，所以后一个静态断言成功。若没有成功，这就是编译器的一个缺陷。

\subsubsubsection{19.6.2\hspace{0.2cm}检查成员类型}

定义像HasSizeTypeT这样的特征会引发一个问题，即如何将特征参数化，以便能够检查成员类型名。

因为没有语言机制来描述“这种”名称，所以目前只能通过宏来实现。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}撰写本文时，C++标准化委员会正在探索以程序可以探索的方式“反映”各种程序实体(如类类型及其成员)的方法。参见第17.9节。
\end{tcolorbox}

在不使用宏的情况下，可以使用泛型Lambda，如第19.6.4节所示。

宏的例子:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{traits/hastype.hpp}
\begin{lstlisting}[style=styleCXX]
#include <type_traits> // for true_type, false_type, and void_t
#define DEFINE_HAS_TYPE(MemType) \
template<typename, typename = std::void_t<>> \
struct HasTypeT_##MemType \
: std::false_type { }; \
template<typename T> \
struct HasTypeT_##MemType<T, std::void_t<typename T::MemType>> \
: std::true_type { } // ; intentionally skipped
✝ 
\end{lstlisting}

每次使用DEFINE\_HAS\_TYPE(MemberType)都会定义一个新的HasTypeT\_MemberType特征。可以使用它来检测类型是否有value\_type或char\_type成员类型:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{traits/hastype.cpp}
\begin{lstlisting}[style=styleCXX]
#include "hastype.hpp"
#include <iostream>
#include <vector>

DEFINE_HAS_TYPE(value_type);
DEFINE_HAS_TYPE(char_type);

int main()
{
	std::cout << "int::value_type: "
	<< HasTypeT_value_type<int>::value << ’\n’;
	std::cout << "std::vector<int>::value_type: "
	<< HasTypeT_value_type<std::vector<int>>::value << ’\n’;
	std::cout << "std::iostream::value_type: "
	<< HasTypeT_value_type<std::iostream>::value << ’\n’;
	std::cout << "std::iostream::char_type: "
	<< HasTypeT_char_type<std::iostream>::value << ’\n’;
}
\end{lstlisting}

\subsubsubsection{19.6.3\hspace{0.2cm}检查非类型成员}

可以修改特征来检查数据成员和(单个)成员函数:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{traits/hasmember.hpp}
\begin{lstlisting}[style=styleCXX]
#include <type_traits> // for true_type, false_type, and void_t

#define DEFINE_HAS_MEMBER(Member) \
template<typename, typename = std::void_t<>> \
struct HasMemberT_##Member \
: std::false_type { }; \
template<typename T> \
struct HasMemberT_##Member<T, std::void_t<decltype(&T::Member)>> \
: std::true_type { } // ; intentionally skipped
\end{lstlisting}

当\&T::Member无效时，使用SFINAE禁用偏特化。要使该结构有效，以下条件必须为true:

\begin{itemize}
\item
成员必须明确地标识T的成员(例如，不能是重载的成员函数名，或同名的多个继承成员的名称)，

\item
成员必须可访问，

\item
成员必须是非类型、非枚举成员(否则前缀\&将无效)，

\item
若T::Member是静态数据成员，其类型不能提供使\&T::Member无效的操作符\&(例如，使该操作符不可访问)。
\end{itemize}

可以这样使用模板:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{traits/hasmember.cpp}
\begin{lstlisting}[style=styleCXX]
#include "hasmember.hpp"

#include <iostream>
#include <vector>
#include <utility>

DEFINE_HAS_MEMBER(size);
DEFINE_HAS_MEMBER(first);

int main()
{
	std::cout << "int::size: "
				<< HasMemberT_size<int>::value << ’\n’;
	std::cout << "std::vector<int>::size: "
				<< HasMemberT_size<std::vector<int>>::value << ’\n’;
	std::cout << "std::pair<int,int>::first: "
				<< HasMemberT_first<std::pair<int,int>>::value << ’\n’;
}
\end{lstlisting}

修改偏特化，以排除\&T::Member不是指向成员类型指针，这并不困难(相当于排除静态数据成员)。类似地，可以排除或要求使用指针成员函数，将特征限制为数据成员或成员函数。

\hspace*{\fill} \\ %插入空行
\noindent
\textbf{检查成员函数}

注意，HasMember特征只检查是否存在一个具有相应名称的成员。若存在两个成员，特征也会失败。若检查重载的成员函数，就可能发生这种情况。例如:

\begin{lstlisting}[style=styleCXX]
DEFINE_HAS_MEMBER(begin);
std::cout << HasMemberT_begin<std::vector<int>>::value; // false
\end{lstlisting}

然而，SFINAE原则可以防止在函数模板声明中同时，创建无效类型和表达式，允许上面的重载扩展到测试表达式是否定义良好。

检查是否可以以特定的方式调用感兴趣的函数，并且即使函数重载也能成功调用。与第19.5节中的IsConvertibleT特征一样，技巧在于构造表达式来检查是否可以在decltype表达式中调用begin()来获取函数模板参数的默认值:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{traits/hasbegin.hpp}
\begin{lstlisting}[style=styleCXX]
#include <utility> // for declval
#include <type_traits> // for true_type, false_type, and void_t

// primary template:
template<typename, typename = std::void_t<>>
struct HasBeginT : std::false_type {
};

// partial specialization (may be SFINAE’d away):
template<typename T>
struct HasBeginT<T, std::void_t<decltype(std::declval<T>().begin())>>
: std::true_type {
};
\end{lstlisting}

这里，我们使用

\begin{lstlisting}[style=styleCXX]
decltype(std::declval<T>().begin())
\end{lstlisting}

测试给定一个T类型的值/对象(使用std::declval来避免使用构造函数)，调用成员begin()是否有效。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}除了decltype(调用表达式)不需要一个非引用、非void的返回类型作为完整的返回类型，这与其他表达式不同。而使用decltype(std::declval<T>().begin(), 0)，因为返回的值不再是decltype操作数的结果，所以要求调用的返回类型完整。
\end{tcolorbox}

\hspace*{\fill} \\ %插入空行
\noindent
\textbf{检查其他表达式}

可以将上述技术用于其他类型的表达式，甚至组合多个表达式。可以测试给定类型T1和T2，是否为这些类型的值定义了小于操作符:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{traits/hasless.hpp}
\begin{lstlisting}[style=styleCXX]
include <utility> // for declval
#include <type_traits> // for true_type, false_type, and void_t

// primary template:
template<typename, typename, typename = std::void_t<>>
struct HasLessT : std::false_type
{
};

// partial specialization (may be SFINAE’d away):
template<typename T1, typename T2>
struct HasLessT<T1, T2, std::void_t<decltype(std::declval<T1>()
					< std::declval<T2>())>>
: std::true_type
{
};
\end{lstlisting}

这里的挑战在于为条件检查定义一个有效的表达式，并使用decltype将其放置在SFINAE中，若表达式无效，则会回退到主模板:

\begin{lstlisting}[style=styleCXX]
decltype(std::declval<T1>() < std::declval<T2>())
\end{lstlisting}

以这种方式检测有效表达式的特征相当健壮:当表达式定义良好时，才会为true，当小于操作符不明确、删除或不可访问时，会正确地返回false。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}C++11扩展SFINAE覆盖无效表达式前，检测特定表达式有效性的技术集中于为测试函数引入新的重载(例如，小于操作符)，该重载具有过于宽松的签名和一个大小怪异的返回类型，以作为一个备用案例。但这种方法容易产生歧义，并会由于访问控制导致错误。
\end{tcolorbox}

可以这样使用这个特征:

\begin{lstlisting}[style=styleCXX]
HasLessT<int, char>::value // yields true
HasLessT<std::string, std::string>::value // yields true
HasLessT<std::string, int>::value // yields false
HasLessT<std::string, char*>::value // yields true
HasLessT<std::complex<double>, std::complex<double>>::value // yields false
\end{lstlisting}

可以使用这个特征要求模板参数T支持小于操作符:

\begin{lstlisting}[style=styleCXX]
template<typename T>
class C
{
	static_assert(HasLessT<T>::value,
				  "Class C requires comparable elements");
	...
};
\end{lstlisting}

由于std::void\_t的性质，可以在一个特征中组合多个约束:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{traits/hasvarious.hpp}
\begin{lstlisting}[style=styleCXX]
#include <utility> // for declval
#include <type_traits> // for true_type, false_type, and void_t

// primary template:
template<typename, typename = std::void_t<>>
struct HasVariousT : std::false_type
{
};

// partial specialization (may be SFINAE’d away):
template<typename T>
struct HasVariousT<T, std::void_t<decltype(std::declval<T>().begin()),
					typename T::difference_type,
					typename T::iterator>>
: std::true_type
{
};
\end{lstlisting}

检测特定语法有效性的特性非常强大，允许模板根据特定操作的存在或不存在来定制行为。这些特征将再次作为SFINAE友好特征的一部分(第19.4.4节)和协助基于类型属性的重载(第20章)。

\subsubsubsection{19.6.4\hspace{0.2cm}使用泛型Lambda检查成员}

第19.4.3节中介绍的isValid Lambda提供了一种更紧凑的技术来检查成员的特征，有助于避免使用宏来处理成员名称。

下面的例子说明了如何定义特征，检查数据成员或类型成员(如first或size\_type)是否存在，或者是否为两个不同类型的对象定义了小于操作符:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{traits/isvalid1.cpp}
\begin{lstlisting}[style=styleCXX]
#include "isvalid.hpp"
#include<iostream>
#include<string>
#include<utility>

int main()
{
	using namespace std;
	cout << boolalpha;
	
	// define to check for data member first:
	constexpr auto hasFirst
		= isValid([](auto x) -> decltype((void)valueT(x).first) {
				});

	cout << "hasFirst: " << hasFirst(type<pair<int,int>>) << ’\n’; // true
	
	// define to check for member type size_type:
	constexpr auto hasSizeType
		= isValid([](auto x) -> typename decltype(valueT(x))::size_type {
				});
			
	struct CX {
		using size_type = std::size_t;
	};
	cout << "hasSizeType: " << hasSizeType(type<CX>) << ’\n’; // true
	
	if constexpr(!hasSizeType(type<int>)) {
		cout << "int has no size_type\n";
		...
	}

	// define to check for <:
	constexpr auto hasLess
		= isValid([](auto x, auto y) -> decltype(valueT(x) < valueT(y)) {
				});
			
	cout << hasLess(42, type<char>) << ’\n’; // yields true
	cout << hasLess(type<string>, type<string>) << ’\n’; // yields true
	cout << hasLess(type<string>, type<int>) << ’\n’; // yields false
	cout << hasLess(type<string>, "hello") << ’\n’; // yields true
}
\end{lstlisting}

因为不能从引用中访问类型成员，所以hasSizeType使用std::decay从传递的x中移除了引用。若跳过该步骤，因为使用了isValidImpl<>()的第二次重载，所以特征将总生成false。

为了能够使用通用泛型语法，将类型作为模板参数，可以再次定义辅助程序:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{traits/isvalid2.cpp}
\begin{lstlisting}[style=styleCXX]
#include "isvalid.hpp"
#include<iostream>
#include<string>
#include<utility>

constexpr auto hasFirst
	= isValid([](auto&& x) -> decltype((void)&x.first) {
				});
			
template<typename T>
using HasFirstT = decltype(hasFirst(std::declval<T>()));
constexpr auto hasSizeType
	= isValid([](auto&& x)
			-> typename std::decay_t<decltype(x)>::size_type {
			});

template<typename T>
using HasSizeTypeT = decltype(hasSizeType(std::declval<T>()));
constexpr auto hasLess
	= isValid([](auto&& x, auto&& y) -> decltype(x < y) {
				});

template<typename T1, typename T2>
using HasLessT = decltype(hasLess(std::declval<T1>(), std::declval<T2>()));

int main()
{
	using namespace std;
	
	cout << "first: " << HasFirstT<pair<int,int>>::value << ’\n’; // true
	
	struct CX {
		using size_type = std::size_t;
	};

	cout << "size_type: " << HasSizeTypeT<CX>::value << ’\n’; // true
	cout << "size_type: " << HasSizeTypeT<int>::value << ’\n’; // false
	
	cout << HasLessT<int, char>::value << ’\n’; // true
	cout << HasLessT<string, string>::value << ’\n’; // true
	cout << HasLessT<string, int>::value << ’\n’; // false
	cout << HasLessT<string, char*>::value << ’\n’; // true
}
\end{lstlisting}

现在

\begin{lstlisting}[style=styleCXX]
template<typename T>
using HasFirstT = decltype(hasFirst(std::declval<T>()));
\end{lstlisting}

允许调用

\begin{lstlisting}[style=styleCXX]
HasFirstT<std::pair<int,int>>::value
\end{lstlisting}

来获取pair的两个整型数，其计算方法如上所述。










